﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Forms;

namespace WinFormsMVC
{
    public class App
    {
        public static App Instance = new App();
        // public Route Route;
        private ViewUserControlCollection viewUserControls = new ViewUserControlCollection();
        private IDictionary<string, string> controllers = new Dictionary<string, string>();
        private Panel _placerPanel;

        private App()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
        }

        public void Initialize(string controller, string action, Panel placerPanel)
        {
            _placerPanel = placerPanel;

            PopulateControllers();
            PopulateViewUserControls();

            Run(new RouteData()
            {
                Controller = controller,
                Action = action
            });

            _placerPanel.FindForm().Show();
        }

        public void CallRoute(string controller, string action)
        {
            CallRoute(controller, action, null);
        }

        public void CallRoute(string controller, string action, object parameters)
        {
            var routeParameters = new Dictionary<string, object>();

            if (parameters != null)
            {
                foreach (PropertyInfo item in parameters.GetType().GetProperties())
                {
                    routeParameters.Add(item.Name, item.GetValue(parameters, null));
                }
            }

            Run(new RouteData() { Controller = controller, Action = action }, routeParameters);
        }

        private void CallRoute(string controller, string action, RouteData routeData, Dictionary<string, object> parameters)
        {
            Run(routeData, parameters);
        }

        public DataResult CallRouteData(string controller, string action)
        {
            return CallRouteData(controller, action, null);
        }

        public DataResult CallRouteData(string controller, string action, RouteData routeData) // TODO - why are both being passed?
        {
            return (DataResult)GetViewResultFromRoute(routeData, null);
        }

        private void Run()
        {
            Run(null);
        }

        private void Run(RouteData routeData)
        {
            Run(routeData, null);
        }

        private void Run(RouteData routeData, Dictionary<string, object> parameters)
        {
            ViewResult result = GetViewResultFromRoute(routeData, parameters);
            if (result.RouteData != null)
            {
                // TODO - handle route redirection
            }

            ViewUserControl userControl = GetViewUserControlFromResult(result);

            if (_placerPanel != null && userControl != null)
            {
                _placerPanel.Controls.Clear();
                userControl.Dock = DockStyle.Fill;
                _placerPanel.Controls.Add(userControl);
            }
        }

        private void PopulateControllers()
        {
            Type[] types = Assembly.GetEntryAssembly().GetTypes();
            foreach (var type in types)
            {
                if (type.IsSubclassOf(typeof(Controller)) && type.Name.EndsWith("Controller"))
                {
                    controllers.Add(type.Name, type.AssemblyQualifiedName);
                }
            }

            controllers.Add("ProgramController", typeof(ProgramController).AssemblyQualifiedName);
        }

        private void PopulateViewUserControls()
        {
            Type[] types = Assembly.GetEntryAssembly().GetTypes();
            foreach (var type in types)
            {
                if (type.IsSubclassOf(typeof(ViewUserControl)) || type.IsSubclassOf(typeof(ViewUserControl)))
                {
                    AddViewUserControl(type);
                }
            }
        }

        private void AddViewUserControl(Type type)
        {
            string controllerName = "";
            foreach (var item in type.Namespace.Split('.'))
            {
                controllerName = item;
            }

            string actionName = type.Name;

            viewUserControls.Add(controllerName, actionName, type.AssemblyQualifiedName);
        }

        public ViewResult GetViewResultFromRoute(RouteData routeData, Dictionary<string, object> parameters)
        {
            Controller controller = GetControllerFromRoute(routeData);
            MethodInfo methodInfo = controller.GetType().GetMethod(routeData.Action);

            if (methodInfo == null)
                throw new InvalidOperationException(string.Format("Could not find action {0}", routeData.Action));

            object[] methodParams = GetMethodParams(methodInfo, parameters);

            if (methodParams != null && methodParams.Length > 0)
                return (ViewResult)methodInfo.Invoke(controller, methodParams);
            else
                return (ViewResult)methodInfo.Invoke(controller, null);
        }

        private object[] GetMethodParams(MethodInfo methodInfo, Dictionary<string, object> parameters)
        {
            ParameterInfo[] methodParameters = methodInfo.GetParameters();

            object[] list = new object[methodParameters.Length];

            foreach (ParameterInfo item in methodParameters)
            {
                if (parameters.ContainsKey(item.Name))
                {
                    list[item.Position] = FormatParameterType(parameters[item.Name], item.ParameterType);
                }
            }

            return list;
        }

        private object FormatParameterType(object parameter, Type type)
        {
            if (type == typeof(string))
                return parameter.ToString();
            else if (type == typeof(int) || type == typeof(Int32))
                return int.Parse(parameter.ToString());
            else if (type == typeof(DateTime))
                return DateTime.Parse(parameter.ToString());
            else if (type == typeof(bool))
                return bool.Parse(parameter.ToString());
            else if (type == typeof(string[]))
                return (string[])parameter;

            return parameter;
        }

        private ViewUserControl GetViewUserControlFromResult(ViewResult result)
        {
            if (result != null && result.GetType() == typeof(EmptyResult))
                return null;

            if (viewUserControls.Count > 0)
            {
                ViewUserControl control = (ViewUserControl)Activator.CreateInstance(Type.GetType(viewUserControls[result.RouteData.Controller, result.RouteData.Action]));
                control.ViewData = result.ViewData;
                return control;
            }
            else
            {
                throw new InvalidOperationException("No ViewForms have been found.");
            }
        }

        private Controller GetControllerFromRoute(RouteData routeData)
        {
            if (controllers.Count > 0)
            {
                string controllerName = routeData.Controller + "Controller";

                if (!controllers.ContainsKey(controllerName))
                    throw new InvalidOperationException(string.Format("Controller not found {0}", controllerName));

                Controller controller = (Controller)Activator.CreateInstance(Type.GetType(controllers[controllerName]));
                controller.RouteData = routeData;
                return controller;
            }

            throw new InvalidOperationException("No Controllers have been found.");
        }
    }
}
